#pragma once
/******************************************************************************************
 * mode:主要用来和数据提供交互，对外提供访问数据的接口
 * 该模块为根据题目list文件，加载所有题目信息到内存中
 * 主要逻辑:
 * 1.打开存放题目的列表文件，将文件内容按行读取，并且创建Question结构体存储题目信息，利用题目编号与结构体构建映射关系
 * 2.提供读取所有题目以及特定题目的接口
 *******************************************************************************************/
#include "../Comm/comm.hpp"
#include "../Comm/log.hpp"
#include "../Comm/util.hpp"

namespace ns_model
{
    using namespace ns_log;
    using namespace ns_util;
    const std::string question_list = "./questions/questions.list"; // 题目列表文件
    const std::string question_path = "./questions/";                // 题目的保存路径

    // 此类的功能为题目的描述
    struct Question
    {
        std::string _number; // 题目的编号，唯一
        std::string _title;  // 题目的标题
        std::string _star;   // 题目的难度
        int _cpu_limit;      // 时间约束
        int _mem_limit;      // 空间约束
        std::string _desc;   // 题目的描述
        std::string _header; // 题目的预设代码
        std::string _tail;   // 题目的测试用例，与header拼接在一起使用
    };
    // 此类的功能为建立题目序号与题目内容的映射关系，并提供对题目的操纵接口
    class Model
    {
        private:
            //题号和题目细节的映射
            std::unordered_map<std::string,Question> _questions;
        public:
            Model(){
                assert(LoadQuestionList(question_list));
            }
            ~Model(){}

            //此函数功能为将题目加载到Model中
            bool LoadQuestionList(const std::string& question_list)
            {
                //加载配置文件 questions.list + 题目编号文件
                //打开文件
                std::ifstream in(question_list);
                if(!in.is_open())
                {
                    LOG(FATAL)<<"加载题库失败，请检查是否存在题库文件"<<endl;
                    return false;
                }
                std::string line;
                //按行读取文件内容
                while(std::getline(in,line))
                {
                    std::vector<std::string> tokens;
                    //切割一行内容
                    StringUtil::SplitString(line,tokens," ");
                    if(tokens.size()!=5)//1 判断回文数 简单 1 30000   一行5个参数用空格隔开
                    {
                        LOG(WARNING)<<"加载部分题目失败: "<<line<<" ,请检查文件格式"<<endl;
                        continue;
                    }
                    //填充字段
                    Question q;
                    q._number = tokens[0];
                    q._title = tokens[1];
                    q._star = tokens[2];
                    q._cpu_limit = atoi(tokens[3].c_str());
                    q._mem_limit = atoi(tokens[4].c_str());

                    std::string path = question_path;
                    path += q._number;
                    path +='/';
                    //读取题目文件信息
                    FileUtil::ReadFile(path+"desc.txt",&(q._desc),true);
                    FileUtil::ReadFile(path+"header.cpp",&(q._header),true);
                    FileUtil::ReadFile(path+"tail.cc",&(q._tail),true);
                    _questions.insert({q._number,q});
                }
                LOG(NORMAL)<<"加载题库成功!"<<endl;
                //关闭文件
                in.close();
                return true;
            }
            //此函数功能获取所有题目
            bool GetAllQuestion(std::vector<Question> *out)
            {
                if(_questions.size()==0)
                {
                    //没有题目
                    LOG(ERROR)<<"获取题库失败"<<endl;
                    return false;
                }
                for(const auto& q:_questions)
                {
                    out->push_back(q.second);
                }
                return true;
            }
            //此函数功能为获取单个指定题目
            bool GetOneQuestion(const std::string& number,Question *q)//输入的为题目的序号
            {
                const auto& iter = _questions.find(number);
                if(iter==_questions.end())
                {
                    //没有找到
                    LOG(ERROR)<<"获取指定题目失败,题目编号：%d"<<number<<endl;
                    return false;
                }
                (*q) = iter->second;
                return true;
            }
    };
}